1/5/24
There are some instances of SDL_Window
and SDL_Renderer
that are required to be referenced throughout the library. A
window
and corresponding renderer
are needed
throughout multiple SDL functions for graphics rendering. Planned
components built on top of SDL like SpriteRenderer
require
ready access to them in order to render objects.
First thought was to make them global, but as global variables are generally discouraged, I explored options to minimize scope to only the classes that would need them. Eventually it was decided to make variables effectively global for the time being as the options turned out fruitless and overcomplicated.
One option was to create a renderer class with SDL objects as
protected static variables so that they may be inherited by new user
components like SpriteRenderer
. This way we limit the scope
of these variables to only classes that need it.
GameRenderer.h
#include <SDL.h>
/**
* Globally available SDL window references for internal use.
*/
class GameRenderer {
public:
// [... methods...]
protected:
/**
* SDL Renderer access
*/
static const int SCREEN_WIDTH = 640;
static const int SCREEN_HEIGHT = 480;
static SDL_Window* window;
static SDL_Renderer* renderer;
static SDL_Surface* screenSurface;
};
However, we’ll find that user facing classes like
SpriteRenderer
cannot inherit from
GameRenderer
in this case, as doing so would require
#include
a header that contains
#include <SDL.h>
, thereby exposing implementation to
the user as well as requiring the library user to also install SDL.
Another option was exploring a variation of the PImpl pattern, where a forward declaration of an implementation class is made within parent class alongside a pointer reference to the class that can be used.
Implementation of an interface will usually change more often than the interface itself. Because private members (i.e. implementation details) are part of a class definition in the header file, changing private members will change the class definition even though there are no public user facing changes to the interface.
This will require recompilation on part of the user even though the only hidden from user implementation details were changed.
The common pattern/solution in cpp is to declare an implementation
class along with a pointer to an instance to the class. Declaration of
private methods/members are now effectively moved to .cpp
files and so modification of private variables would only require
recompilation of the library and not any library users applications.
While the library being built is small and compilation time and dependencies are not really an issue, I did find that the further hiding the implementation details aspect of this pattern potentially useful for solving the issue with [[#Protected Static]]. At first glance this seemed like a good way to have protected SDL variables without them being part of the declaration header.
GameRenderer.h
class GameRenderer {
public:
// [... methods...]
protected:
/**
* Forward declaration to hide SDL implementation details.
*/
class GameRendererImpl;
static std::unique_ptr<GameRendererImpl> pImpl;
};
GameRenderer.cpp
class GameRenderer::GameRendererImpl
{
public:
();
GameRendererImpl~GameRendererImpl();
const int SCREEN_WIDTH = 640;
const int SCREEN_HEIGHT = 480;
* window = nullptr;
SDL_Window* renderer = nullptr;
SDL_Renderer* screenSurface = nullptr;
SDL_Surface};
//______GameRenderer definitions______//
//
// [...code...]
//___GameRendererImpl definitions_____//
::GameRendererImpl::GameRendererImpl()
GameRenderer{
// [...SDL code...]
}
::GameRendererImpl::~GameRendererImpl()
GameRenderer{
// [...SDL code...]
}
However, while SDL implementation details are hidden from the user,
we encounter an issue where implementation details of this class are
also hidden from other classes of the library. Other classes defined in
other .cpp
files that inherit from
GameRenderer
also have no way of accessing the SDL members
of the GameRendererImpl
instance as the implementation is
no longer declared in the header.
This appears to be the tradeoff of using the pImpl pattern, and in this case prohibits it from being a viable way of reducing the scope of these SDL window and renderer variables.
With both these options not working, I opted to make the variables in
effect global by encasing them as static variables in the
GameRenderer
class. The header for this class would not be
included in the library, and only included in library implementation
files to those that need it.
This way SDL is still hidden from the user, and library files that require access to the SDL variables need only include this header file.
While this could have been done in the first place to save time, the search process for a smaller scope solution was still a nice learning experience on how libraries need to be structured and various techniques available to hide implementation details.
GameRenderer.h
#include <SDL.h>
/**
* Globally available SDL window references for internal use.
*/
class GameRenderer {
public:
// [... methods...]
/**
* SDL Renderer access
*/
static const int SCREEN_WIDTH = 640;
static const int SCREEN_HEIGHT = 480;
static SDL_Window* window;
static SDL_Renderer* renderer;
static SDL_Surface* screenSurface;
};